﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Collections;

namespace DiagramDesigner
{
    /// <summary>Enables the selection of items by a specified rectangle.</summary>
    internal sealed class ItemsControlSelector
    {
        private readonly ItemsControl itemsControl;
        private Rect previousArea;
        public ScrollContentPresenter scp;
        ArrayList lastSelected = new ArrayList();
        ArrayList curSelected = new ArrayList();

        /// <summary>
        /// Initializes a new instance of the ItemsControlSelector class.
        /// </summary>
        /// <param name="itemsControl">
        /// The control that contains the items to select.
        /// </param>
        /// <exception cref="ArgumentNullException">itemsControl is null.</exception>
        public ItemsControlSelector(ItemsControl itemsControl)
        {
            if (itemsControl == null)
            {
                throw new ArgumentNullException("itemsControl");
            }
            this.itemsControl = itemsControl;
        }

        /// <summary>
        /// Resets the cached information, allowing a new selection to begin.
        /// </summary>
        public void Reset()
        {
            this.previousArea = new Rect();
            lastSelected.Clear();
        }

        /// <summary>
        /// Scrolls the selection area by the specified amount.
        /// </summary>
        /// <param name="x">The horizontal scroll amount.</param>
        /// <param name="y">The vertical scroll amount.</param>
        public void Scroll(double x, double y)
        {
            //this.previousArea.Offset(-x, -y);
        }

        /// <summary>
        /// Updates the controls selection based on the specified area.
        /// </summary>
        /// <param name="area">
        /// The selection area, relative to the control passed in the contructor.
        /// </param>
        public void UpdateSelection(Rect area)
        {
            //todo: check for null
            FrameworkElement ite = this.itemsControl.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement;
            ListBox lb = itemsControl as ListBox;

            curSelected.Clear();

            if (area.Width == 0 || area.Height == 0)
            {
                object item = ListBoxSelector.GetElementFromPoint(lb, area.TopLeft);

                if (item != null)
                {
                    if ((Keyboard.Modifiers & ModifierKeys.Control) != 0)
                    {
                        if (lb.SelectedItems.Contains(item))
                        {
                            lb.SelectedItems.Remove(item);
                        }
                        else
                        {
                            lb.SelectedItems.Add(item);
                            curSelected.Add(item);
                        }
                    }
                    else
                    {
                        ((ListBox)itemsControl).SelectedItem = item;
                        curSelected.Clear();
                        curSelected.Add(item);
                    }
                }

            }
            else
            {
                //correct positioning to pixel centers
                area.X += 0.5;
                area.Y += 0.5;

                Point tl = this.scp.ScrollOwner.TransformToDescendant(ite).Transform(area.TopLeft);
                Point br = this.scp.ScrollOwner.TransformToDescendant(ite).Transform(area.BottomRight);
                Rect areaT = new Rect(tl, br);

                // Check each item to see if it intersects with the area.
                for (int i = 0; i < this.itemsControl.Items.Count; i++)
                {
                    FrameworkElement lbItem = this.itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
                    if (lbItem != null)
                    {
                        // Get the bounds in the parent's co-ordinates.
                        Rect itemRect = VisualTreeHelper.GetDescendantBounds(lbItem);
                        Rect itemBounds = lbItem.TransformToAncestor(itemsControl).TransformBounds(itemRect);

                        // Only change the selection if it intersects with the area
                        // (or intersected i.e. we changed the value last time).                        
                        if (itemRect.IntersectsWith(areaT))
                        {
                            bool intersects = false;

                            RectangleGeometry rgm = new RectangleGeometry(areaT);

                            VisualTreeHelper.HitTest(lbItem, null,
                                new HitTestResultCallback(
                                    hitTestResult =>
                                    {
                                        //if (!item.IsAncestorOf(hitTestResult.VisualHit))
                                        //    return HitTestResultBehavior.Continue;
                                        //intersects = ((GeometryHitTestResult)hitTestResult).IntersectionDetail != IntersectionDetail.Empty;
                                        intersects = ((GeometryHitTestResult)hitTestResult).IntersectionDetail == IntersectionDetail.FullyInside;
                                        return HitTestResultBehavior.Stop;
                                    }
                                    ),
                                new GeometryHitTestParameters(rgm));

                            if (intersects)
                            {
                                Selector.SetIsSelected(lbItem, true);
                                curSelected.Add(lb.Items[i]);
                            }
                            else
                            {
                                if (lastSelected.Contains(lb.Items[i]))
                                    Selector.SetIsSelected(lbItem, false);
                            }
                        }
                        else
                        {
                            if (lastSelected.Contains(lb.Items[i]))
                                Selector.SetIsSelected(lbItem, false);
                        }

                    }
                }
                //this.previousArea = areaT;

            }
            this.lastSelected.Clear();
            lastSelected.AddRange(curSelected);
        }
    }
}
